#!/bin/bash
set -e

init_repo(){
  rm -rf "$repo_path"
  mkdir "$repo_path"
  git -C "$repo_path" -c init.defaultBranch=master init >/dev/null
  touch "$repo_path/README.md"
  git -C "$repo_path" add "README.md"
  git -C "$repo_path" commit -m "first commit"
  git -C "$repo_path" remote add origin "$repo"
  git -C "$repo_path" push -u origin master
}

load_configure(){
  # 读取设置
  local conf="$HOME/.config/fkcloud/backends/gitctl.env"
  source "$conf"
  local configs=(
    username
    email
    repo
    raw_url
  )
  local config
  for config in ${configs[@]}; do
  eval "value=\"\$$config\""
  if [ ! "$value" ]; then
    echo "$config is not set in $conf" >&2
    return 1
  fi
  done
  
  # 拉取仓库
  tmp_dir="/tmp/fkcloud/backends/gitctl"
  mkdir -p "$tmp_dir"
  repo_path="$tmp_dir/$(echo "$repo" | md5sum | cut -c-16)"
  if [ ! -d "$repo_path" ]; then
    git clone --single-branch -b master "$repo" "$repo_path" >/dev/null 2>&1 || init_repo
  else
    git -C "$repo_path" checkout master >/dev/null 2>&1 || init_repo
    git -C "$repo_path" pull >/dev/null || init_repo
  fi
  
  # 确保 root 目录本地存在
  mkdir -p "$repo_path/root"
  directory="$repo_path/root"
}

get_repo_file_raw_url(){
  local branch_name="$1"
  local file_path="$2"
  printf "$raw_url\n" "$branch_name" "$file_path"
}

urlencode(){
  [ "$1" ] || return 1
  local LANG=C i x
  for (( i = 0; i < ${#1}; i++ )); do
    x="${1:i:1}"
    [[ "${x}" == [a-zA-Z0-9.~_-] ]] && echo -n "${x}" || printf '%%%02X' "'${x}"
  done
  printf '\n'
}

push_file_to_branch(){
  local src="$1"
  local dst="$2"  # 必须是被格式化过的 dst
  local branch_name="$3"
  local file_name="$4"
  local commit_descripton="$5"
  if git -C "$repo_path" rev-parse --verify "$branch_name" >/dev/null 2>&1; then
    git -C "$repo_path" branch -D "$branch_name"
  fi
  git -C "$repo_path" checkout -b "$branch_name" "$(git -C "$repo_path" rev-list --max-parents=0 HEAD)"
  install -Dm644 "$src" "$repo_path/file/$file_name"
  git -C "$repo_path" add "$repo_path/file/$file_name"
  git -C "$repo_path" -c user.name="$username" -c user.email="$email" commit -m "$commit_descripton"
  git -C "$repo_path" push -f -u origin "$branch_name"
  git -C "$repo_path" checkout master
  git -C "$repo_path" branch -D "$branch_name"
}

push_commit_root_to_master(){
  local commit_descripton="$1"
  git -C "$repo_path" add "root"
  git -C "$repo_path" -c user.name="$username" -c user.email="$email" commit -m "$commit_descripton" || true
  git -C "$repo_path" push
}

clean_root(){
  git -C "$repo_path" clean -df
  git -C "$repo_path" restore "root"
}

make_file_meta(){
  local branch_name="$1"
  local file_name="$2"
  printf '{"branch_name": "%s", "file_name": "%s"}\n' "$branch_name" "$file_name"
}

parse_file_meta(){
  jq -r '"file_name=\"\(.file_name)\"\nbranch_name=\"\(.branch_name)\""'
}

cmd_ls(){
  local path="$1"
  local list="$(cd "$directory" && eval find "./$path" -maxdepth 1 -mindepth 1 | cut -c2-)"
  local obj
  for obj in $list; do
    if [ -d "$directory/$obj" ]; then
      printf '%s/\n' "$obj"
    else
      printf '%s\n' "$obj"
    fi
  done | sed 's/\/\{2,\}/\//g'
}

cmd_url(){
  local path="$1"
  if [ -f "$directory/$path" ]; then
    path="$(echo "/$path" | sed 's/\/\{2,\}/\//g')"
    local file_name branch_name
    eval "$(curl -s "$(get_repo_file_raw_url "master" "root$path")" | parse_file_meta)"
    get_repo_file_raw_url "$branch_name" "file/$file_name"
  elif [ -d "$directory/$path" ]; then
    printf 'Can not pull directory.\n' >&2
    return 1
  else
    printf 'No such file: %s\n' "$path" >&2
    return 1
  fi
}

cmd_find(){
  local path="$1"
  local list="$(cd "$directory" && eval find "./$path" -mindepth 1 -type f | cut -c2-)"
  local obj
  for obj in $list; do
    if [ -d "$directory/$obj" ]; then
      printf '%s/\n' "$obj"
    else
      printf '%s\n' "$obj"
    fi
  done | sed 's/\/\{2,\}/\//g'
}

cmd_pull(){
  local src="$1"
  local dst="$2"
  [ "$dst" ] || return 1
  if [ -f "$directory/$src" ]; then
    curl "$(cmd_url "$src")" > "$dst"
  elif [ -d "$directory/$src" ]; then
    # 整理源文件
    local src="$(echo "/$src" | sed 's/\/\{2,\}/\//g')"
    
    # 获取文件列表
    local list="$(cd "$directory" && eval find ".$src" -type f | cut -c2-)"
    
    # 下载到临时目录
    tmp_dir="$(mktemp -d)"
    for file in $list; do
      mkdir -p "$(dirname "$tmp_dir$file")"
      curl "$(cmd_url "$file")" > "$tmp_dir$file"
    done
    
    # 移动到目标目录
    mv "$tmp_dir"/* "$dst"
    rm -rf "$tmp_dir"
  else
    printf 'No such file: %s\n' "$src" >&2
    return 1
  fi
}

cmd_cat(){
  local path="$1"
  curl -s "$(cmd_url "$1")"
}

cmd_push(){
  local src="$1"
  local dst="$2"
  mkdir -p "$(dirname "$directory/$dst")"
  if [ -d "$src" ]; then
    # 确保目标路径不存在同名文件
    if [ -f "$directory/$dst" ]; then
      echo "File already exists: $dst" >&2
      return 1
    fi
    
  	# 整理目标文件夹
  	dst="$(echo "/$dst/" | sed 's/\/\{2,\}/\//g')"
    
    local dst_files=()
    local list="$(find "$src" -type f)"
    local file
    for file in $list; do
      # 整理目标文件
      local dst_file="$(echo "/$dst${file:${#src}}" | sed 's/\/\{2,\}/\//g')"
      dst_files+=("$dst_file")
      local file_name="${file##*/}"
      
      # 推送分支
      branch_name="$(date '+%s.%N')-$(urlencode "$file_name")"
      push_file_to_branch "$file" "$dst_file" "$branch_name" "$file_name" "fkcloud push $dst"
      
      # 添加记录
      if ! install -Dm644 <(make_file_meta "$branch_name" "$file_name") "$directory$dst_file"; then
        clean_root
        return 1
      fi
    done
    
    # 推送 master
    push_commit_root_to_master "fkcloud push $dst"
  elif [ -e "$src" ]; then
  	# 整理目标文件
    if [ -d "$directory/$dst" ]; then
      dst="$(echo "/$dst/${src##*/}" | sed 's/\/\{2,\}/\//g')"
    else
      dst="$(echo "/$dst" | sed 's/\/\{2,\}/\//g')"
    fi
    local file_name="${dst##*/}"
    
    # 推送分支
    branch_name="$(date '+%s.%N')-$(urlencode "$file_name")"
    push_file_to_branch "$src" "$dst" "$branch_name" "$file_name" "fkcloud push $dst"
    
    # 添加记录
    install -Dm644 <(make_file_meta "$branch_name" "$file_name") "$directory$dst"
    push_commit_root_to_master "fkcloud push $dst"
  else
    printf 'No such file or directory: %s\n' "$src" >&2
  fi
}

cmd_mv(){
  local src="$1"
  local dst="$2"
  
  # 整理目标文件
  src="$(echo "/$src" | sed 's/\/\{2,\}/\//g')"
  dst="$(echo "/$dst" | sed 's/\/\{2,\}/\//g')"
  
  # 移动并提交
  mkdir -p "$(dirname "$directory$dst")"
  mv "$directory$src" "$directory$dst"
  push_commit_root_to_master "fkcloud mv $src -> $dst"
}

cmd_rm(){
	# 整理目标文件
  local del_dsts=()
  for dst in $@; do
    if [ -f "$directory/$dst" ]; then
      dst="$(echo "/$dst" | sed 's/\/\{2,\}/\//g')"
      del_dsts+=("$dst")
    elif [ -d "$directory/$dst" ]; then
      dst="$(echo "/$dst" | sed 's/\/\{2,\}/\//g')"
      del_dsts+=("$dst")
    else
      printf 'No such file: %s\n' "$path" >&2
      return 1
    fi
  done
	
  # 获取所有要删除的分支
  local del_args=()
  for dst in ${del_dsts[@]}; do
    if [ -f "$directory/$dst" ]; then
      # 获取分支名
      local file_name branch_name
      eval "$(curl -s "$(get_repo_file_raw_url "master" "root$dst")" | parse_file_meta)"
      
      # 记录分支名
      del_args+=(-d "$branch_name")
    elif [ -d "$directory/$dst" ]; then
      # 获取文件列表
      local list="$(cd "$directory" && eval find ".$dst" -type f | cut -c2-)"
      
      # 获取所有分支名
      local obj
      for obj in $list; do
        local file_name branch_name
        eval "$(curl -s "$(get_repo_file_raw_url "master" "root$obj")" | parse_file_meta)"
        del_args+=(-d "$branch_name")
      done
    fi
  done
  
  # 删除记录
  for dst in ${del_dsts[@]}; do
    rm -rf "$directory$dst"
  done
  
  # 提交删除
  push_commit_root_to_master "fkcloud rm ${del_dsts[*]}"
  
  # 删除所有相关分支
  git -C "$repo_path" push origin "${del_args[@]}"
}

cmd_help(){
  printf 'Usage: %s COMMAND

Commands:
  ls
  url
  pull
  push
  cat
  mv
  rm
' "$0"
}

main(){
  local cmd="$1"
  shift || true
  [ "$cmd" ] || cmd=help
  if ! type "cmd_$cmd" >/dev/null 2>&1; then
    cmd_help
    return 1
  fi
  load_configure
  cmd_$cmd "$@"
}

main "$@"

